#include "main.h"
#include "math.h"

Object funcs::get_first(Object obj)
{
    Object ret;
    if (obj->get_type() == "#<Number>")
	ret = Number(new number(obj->get_num()));
    else if (obj->get_type() == "#<String>")
	ret = String(new string(obj->to_string()));
    else
	throw Error(new error(error::opera));
    return ret;
}

Object funcs::quote(Environment env)
{
    Cons ret(new cons(env));
    Object obj = arg->get_car(), rp = ret;

    try {
	if (obj->get_car()->get_car() == env->get_value("eval"))
	    ret->set_car(obj->get_car()->eval(env));
	else
	    ret->set_car(obj->get_car());
    } catch (...) {
	ret->set_car(arg->get_car()->get_car());
    }
    for (obj = obj->get_cdr(); obj != env->get_value("false"); obj = obj->get_cdr()) {
	rp->set_cdr(Cons(new cons(env)));
	rp = rp->get_cdr();
	try {
	    if (obj->get_car()->get_car() == env->get_value("eval"))
		rp->set_car(obj->get_car()->eval(env));
	} catch (...) {
	    rp->set_car(obj->get_car());
	}
    }
    return ret;
}

Object funcs::define(Environment env)
{
    env->bind(arg->get_car(), arg->get_cdr()->get_car());
    return arg->get_car();
}

Object funcs::plus(Environment env)
{
    Object ret = get_first(arg->get_car()->eval(env));

    for (Object obj = arg->get_cdr(); obj != env->get_value("false"); obj = obj->get_cdr())
	*ret += obj->get_car()->eval(env);
    return ret;
}

Object funcs::minus(Environment env)
{
    Object ret = get_first(arg->get_car()->eval(env));

    if (arg->get_cdr() == env->get_value("false"))
	ret->set_num(-ret->get_num());
    else
	for (Object obj = arg->get_cdr(); obj != env->get_value("false"); obj = obj->get_cdr())
	    *ret -= obj->get_car()->eval(env);
    return ret;
}

Object funcs::mul(Environment env)
{
    Object ret = get_first(arg->get_car()->eval(env));

    for (Object obj = arg->get_cdr(); obj != env->get_value("false"); obj = obj->get_cdr())
	*ret *= obj->get_car()->eval(env);
    return ret;
}

Object funcs::div(Environment env)
{
    Object ret = get_first(arg->get_car()->eval(env));

    if (arg->get_cdr() == env->get_value("false"))
	ret->set_num(1 / ret->get_num());
    else
	for (Object obj = arg->get_cdr(); obj != env->get_value("false"); obj = obj->get_cdr())
	*ret /= obj->get_car()->eval(env);
    return ret;
}

Object funcs::mod(Environment env)
{
    Object ret = get_first(arg->get_car()->eval(env));

    for (Object obj = arg->get_cdr(); obj != env->get_value("false"); obj = obj->get_cdr())
	*ret %= obj->get_car()->eval(env);
    return ret;
}

Object funcs::equal(Environment env)
{
    Object ret = get_first(arg->get_car()->eval(env));

    for (Object obj = arg->get_cdr(); obj != env->get_value("false"); obj = obj->get_cdr())
	if (*ret == obj->get_car()->eval(env))
	    ret = obj->get_car()->eval(env);
	else
	    return env->get_value("false");
    return ret;
}

Object funcs::noteq(Environment env)
{
    Object ret = get_first(arg->get_car()->eval(env));

    for (Object obj = arg->get_cdr(); obj != env->get_value("false"); obj = obj->get_cdr())
	if (*ret != obj->get_car()->eval(env))
	    ret = obj->get_car()->eval(env);
	else
	    return env->get_value("false");
    return ret;
}

Object funcs::lss(Environment env)
{
    Object ret = get_first(arg->get_car()->eval(env));

    for (Object obj = arg->get_cdr(); obj != env->get_value("false"); obj = obj->get_cdr())
	if (*ret < obj->get_car()->eval(env))
	    ret = obj->get_car()->eval(env);
	else
	    return env->get_value("false");
    return ret;
}

Object funcs::gtr(Environment env)
{
    Object ret = get_first(arg->get_car()->eval(env));

    for (Object obj = arg->get_cdr(); obj != env->get_value("false"); obj = obj->get_cdr())
	if (*ret > obj->get_car()->eval(env))
	    ret = obj->get_car()->eval(env);
	else
	    return env->get_value("false");
    return ret;
}

Object funcs::lsseq(Environment env)
{
    Object ret = get_first(arg->get_car()->eval(env));

    for (Object obj = arg->get_cdr(); obj != env->get_value("false"); obj = obj->get_cdr())
	if (*ret <= obj->get_car()->eval(env))
	    ret = obj->get_car()->eval(env);
	else
	    return env->get_value("false");
    return ret;
}

Object funcs::gtreq(Environment env)
{
    Object ret = get_first(arg->get_car()->eval(env));

    for (Object obj = arg->get_cdr(); obj != env->get_value("false"); obj = obj->get_cdr())
	if (*ret >= obj->get_car()->eval(env))
	    ret = obj->get_car()->eval(env);
	else
	    return env->get_value("false");
    return ret;
}

Object funcs::apply(Environment env)
{
    Cons ls(new cons(env)), l2(new cons(env));

    ls->set_car(arg->get_car());
    ls->set_cdr(l2);
    l2->set_car(arg->get_cdr()->get_car()->get_car());
    l2->set_cdr(arg->get_cdr()->get_car()->get_cdr());
    return ls->eval(env);
}

Object funcs::andopt(Environment env)
{
    Object ret = arg->get_car()->eval(env);

    if (ret == env->get_value("false"))
	return env->get_value("false");
    for (Object obj = arg->get_cdr(); obj != env->get_value("false"); obj = obj->get_cdr())
	if (obj->get_car()->eval(env) != env->get_value("false"))
	    ret = obj->get_car()->eval(env);
	else
	    return env->get_value("false");
    return ret;
}

Object funcs::oropt(Environment env)
{
    if (arg->get_car()->eval(env) != env->get_value("false"))
	return arg->get_car()->eval(env);
    for (Object obj = arg->get_cdr(); obj != env->get_value("false"); obj = obj->get_cdr())
	if (obj->get_car()->eval(env) != env->get_value("false"))
	    return obj->get_car()->eval(env);
    return env->get_value("false");
}

Object funcs::eval(Environment env)
{
    Number num;
    Object obj;

    if (!isset)
	return String(new string(to_string()));
    if (to_string() == "quote")
	return quote(env);
    else if (to_string() == "eval")
	return arg->get_car()->eval(env);
    else if (to_string() == "define")
	return define(env);
    else if (to_string() == "if")
	return arg->get_car()->eval(env) == env->get_value("false") ? arg->get_cdr()->get_cdr()->get_car()->eval(env) : arg->get_cdr()->get_car()->eval(env);
    else if (to_string() == "+")
	return plus(env);
    else if (to_string() == "-")
	return minus(env);
    else if (to_string() == "*")
	return mul(env);
    else if (to_string() == "/")
	return div(env);
    else if (to_string() == "%")
	return mod(env);
    else if (to_string() == "sqrt")
	return Number(new number(sqrt(get_first(arg->get_car()->eval(env))->get_num())));
    else if (to_string() == "exp")
	return Number(new number(pow(get_first(arg->get_car()->eval(env))->get_num(), arg->get_cdr()->get_car()->eval(env)->get_num())));
    else if (to_string() == "==")
	return equal(env);
    else if (to_string() == "!=")
	return noteq(env);
    else if (to_string() == "<")
	return lss(env);
    else if (to_string() == ">")
	return gtr(env);
    else if (to_string() == "<=")
	return lsseq(env);
    else if (to_string() == ">=")
	return gtreq(env);
    else if (to_string() == "apply")
	return apply(env);
    else if (to_string() == "and")
	return andopt(env);
    else if (to_string() == "or")
	return oropt(env);
    throw Error(new error(error::undef));
}
